package org.windowkit.toolkit.widget.combo;

import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.ItemSelectable;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.ItemListener;
import java.awt.event.KeyListener;

import javax.swing.JComboBox;
import javax.swing.JComponent;

import org.ddevil.data.Filter;
import org.windowkit.toolkit.event.EventDispatcher;

/**
 * Base class for our combobox fields.
 * @author Eric Lundin
 */
public abstract class AbstractComboBoxField extends JComponent implements
    ActionListener, ItemSelectable {

	private static final long serialVersionUID = 6980188262396678414L;

	/** The combobox itself. */
    protected ComboBox combobox;

    /**
     * Listens for focus lost events so we can close the popup menu
     */
    private SComboBoxFocusListener comboFocusListener;

    /**
     * Listens for events to dispatch.
     */
    private EventDispatcher eventDispatcher;

    /** Da model */
    protected AdvancedComboModel model;


    /**
     *
     */
    public AbstractComboBoxField(final AdvancedComboModel model) {
        this(model, false, null, 10);
    }

    public AbstractComboBoxField(final AdvancedComboModel model, final boolean editable, final String selOption, final int width){
        initComponents(model, editable, selOption, width);
        initSelf();
    }

    /**
     * Add the components so they are visible.
     */
    private void initSelf() {
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.fill = GridBagConstraints.HORIZONTAL;
        gbc.weightx = 1;
        setLayout(new GridBagLayout());
        add(combobox, gbc);
    }

    /**
     * Initialize the various components that make up this component.
     *
     * @param options
     *            The options to display in the combobox.
     * @param data
     *            The data to back the options with.
     * @param isEditable
     *            Whether to allow the user to input their own values.
     * @param defaultOption
     *            The option which should initially be selected. If no option
     *            should be selected pass null.
     * @param componentID
     *            A unique component ID for this component.
     */
    private void initComponents(final AdvancedComboModel mod,
            final boolean isEditable, final String defaultOption, final int textWidth) {

        this.model = mod;

        // Create combo box
        combobox = new ComboBox(model);
        combobox.putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE);

        // Register this class with an event dispatcher
        combobox.addActionListener(this);
        combobox.addNarrowedActionListener(eventDispatcher = new EventDispatcher(this));

        //set a filter on the action events that will get redispatched
        eventDispatcher.setActionEventFilter(new Filter<ActionEvent>(){
            public boolean accept(final ActionEvent evt) {
                return evt.getActionCommand().equals("comboboxSelectionMade");
            }
        });

        combobox.addFocusListener(eventDispatcher);
        combobox.addKeyListener(eventDispatcher);
        combobox.addItemListener(eventDispatcher);

        // Listner for focus lost to close popup
        combobox.addFocusListener(comboFocusListener = new SComboBoxFocusListener());

        // Add item listener to listen for edits.
        combobox.addItemListener(createEditItemListener());

        // Init combo box settings.
        if (defaultOption == null || defaultOption.trim() == "") {
			combobox.setSelectedIndex(-1);
		}
		else {
			combobox.setSelectedIndex(model.getIndexOf(defaultOption));
		}

        combobox.setTextWidth(textWidth);
        combobox.setEditable(isEditable);


    }

    /**
     * Get the currently selected option.
     *
     * @return The option which is currently selected.
     */
    public String getSelectedLabel() {
        Object item = model.getSelectedItem();
        return item != null ? item.toString() : null ;
    }

    /**
     * {@inheritDoc}
     */
    public Object[] getSelectedObjects() {
        return combobox.getSelectedObjects();
    }

    /**
     * Provide a hint as to how wide to render the combobox.
     *
     * @param width
     *            The width in number of characters.
     */
    public void setTextWidth(final int width) {
        combobox.setTextWidth(width);
    }

    /**
     * Returns the index-position of the specified object in the list.
     *
     * @param item
     *            The item to determine the index of.
     * @return an index representing the position of the item within the model,
     *         -1 if the item does not exist.
     */
    public int getIndexOf(final Object item) {
        return model.getIndexOf(item);
    }

    /**
     * Returns true if the specified object exists in the combo box model,
     * false, otherwise.
     *
     * @param item
     * @return A boolean indicating whether or not the specified object exists
     *         in the model.
     */
    public boolean contains(final Object item) {
        return getIndexOf(item) >= 0;
    }


    public boolean isDataSelected() {
        return !(getSelectedLabel() == null);
    }

    /**
     * Returns whether the combobox is enabled.
     *
     * @return true If enabled, false otherwise.
     */
    @Override
    public boolean isEnabled() {
        return combobox.isEnabled();
    }

    /**
     * Returns whether the user is allowed to input their own options.
     *
     * @return True if the combobox is editable. False otherwise.
     */
    public boolean isEditable() {
        return combobox.isEditable();
    }

    /**
     * Set whether the user is allowed to enter their own value in the combobox.
     *
     * @param isEditable
     *            True if the user can enter their own value. False otherwise.
     */
    public void setEditable(final boolean isEditable) {
        combobox.setEditable(isEditable);
        if (isEditable) {
            combobox.getEditor().getEditorComponent().addFocusListener(
                    comboFocusListener);
        } else {
            combobox.getEditor().getEditorComponent().removeFocusListener(
                    comboFocusListener);
        }
    }

    /**
     * Set the given option to be the selected option.
     *
     * @param option
     *            The option to select in the combo box.
     */
    public void setSelectedOption(final String option) {
        if(option == null || option.equals("")){
            combobox.setSelectedIndex(-1);
        }else{
            combobox.setSelectedItem(option);
        }

    }

    /**
     * Set the given index to be the selected option.
     *
     * @param index
     *            The index of the option to select in the combo box.
     */
    public void setSelectedOption(final int index) {
        combobox.setSelectedIndex(index);
    }

    /**
     * Sets whether this component is enabled.
     *
     * @param isEnabled
     *            True if this component is enabled, false otherwise.
     */
    @Override
    public void setEnabled(final boolean isEnabled) {
        combobox.setEnabled(isEnabled);
    }

    /**
     * Sets the maximum number of options the combobox will display. If there
     * are more options than the combobox is allowed to display a scrollbar will
     * be used.
     *
     * @param maxRows
     *            The maximum number of rows to display.
     */
    public void setMaximumRowCount(final int maxRows) {
        combobox.setMaximumRowCount(maxRows);
    }

    /**
     * Removes all elements and data.
     */
    public void clear() {
        model.removeAllElements();
        setSelectedOption(-1);
    }

    public void clearValue() {
        setSelectedOption(-1);
    }

    /**
     * {@inheritDoc}
     */
    public void actionPerformed(final ActionEvent e) {
        if (e.getActionCommand().equals("comboBoxEdited")) {
            addEditedValue(e);
        }
    }


    private void addEditedValue(final ActionEvent e) {
        Object newItem =  ((JComboBox) e.getSource()).getSelectedItem();
        if (newItem != null) {
            if (model.getIndexOf(newItem) == -1) {
                model.addElement(newItem);
                setSelectedOption(newItem.toString());
            }
        }
    }


    /**
     * This class is responsible for cleaning up the combobox when focus is
     * lost.
     *
     * @author Eric Lundin
     */
    class SComboBoxFocusListener implements FocusListener {

        /**
         * {@inheritDoc}
         */
        public void focusGained(final FocusEvent e) {
            // Do Nothing
        }

        /**
         * {@inheritDoc}
         */
        public void focusLost(final FocusEvent e) {
            if (combobox.isPopupVisible()) {
				combobox.hidePopup();
			}
        }
    }

    /**
     * Create an ItemListener that will be responsible for adding items to
     * the combo model which the user has added.
     * @return
     */
    protected abstract ItemListener createEditItemListener();


    /**
     * {@inheritDoc}
     */
    @Override
    public void requestFocus() {
        if (!combobox.hasFocus()) {
            combobox.requestFocus();
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean requestFocus(final boolean temporary) {
        boolean ret = true;
        if (!combobox.hasFocus()) {
            ret = combobox.requestFocus(temporary);
        }
        return ret;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean requestFocusInWindow() {
        boolean ret = true;
        if (!combobox.hasFocus()) {
            ret = combobox.requestFocusInWindow();
        }
        return ret;
    }

    /**
     * Adds an action listener to this combobox. The listener will receive an
     * event when a selection is made. If the combobox is editable the event
     * will be sent when editing has stopped.
     *
     * @param listener
     *            The ActionListener to add.
     */
    public void addActionListener(final ActionListener listener) {
        eventDispatcher.addActionListener(listener);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void addFocusListener(final FocusListener fl) {
        eventDispatcher.addFocusListener(fl);
    }

    /**
     * Add and ItemListener to this combobox
     *
     * @param il
     *            The ItemListener to add.
     */
    public void addItemListener(final ItemListener il) {
        eventDispatcher.addItemListener(il);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void addKeyListener(final KeyListener kl) {
        eventDispatcher.addKeyListener(kl);
    }

    /**
     * Removes an ActionListener.
     * @param listener
     */
    public void removeActionListener(final ActionListener listener) {
        eventDispatcher.removeActionListener(listener);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void removeFocusListener(final FocusListener fl) {
        eventDispatcher.removeFocusListener(fl);
    }

    /**
     * remove and ItemListener to this combobox
     *
     * @param il
     *            The ItemListener to remove.
     */
    public void removeItemListener(final ItemListener il) {
        eventDispatcher.removeItemListener(il);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void removeKeyListener(final KeyListener kl) {
        eventDispatcher.removeKeyListener(kl);
    }

}
